﻿using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.EventArgs;
using RaceHFTools.Utils;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace RaceHFTools.BluetoothLE.Bean.Module
{
    public enum DFU_TYPE
    {
        BOOTLOADER,
        FIRMWARE,
        GNSS,
        AGPS,
        FILE,

        ERROR = 0xff
    };

    public enum DFU_OPERATION
    {
        INQUIRY,
        RESUME,
        START,
        SYNC,
        STOP,
        ACTIVE,
        TERMINAL,

        ERROR = 0xff
    };

    public enum DFU_ACK
    {
        OK,
        DONE,
        EXIT,
        ERR_BUSY,
        ERR_OPERATE,
        ERR_PARAM,
        ERR_PROCESS,
        ERR_HARD,
        ERR_RAM,
        ERR_OVERFLOW,
        ERR_OPENFILE,
        ERR_CRC,

        UNKOWN = 0xff
    };

    public enum DFU_INQUIRY
    {
        VERSION,
        BULKMAX,
        OPERATION,
        STATE,
        IMAGE,
        BUFFER,
        DOWNLOAD,

        ERROR = 0xff
    };

    public class Upgrade
    {
        public delegate void ProgressEvent(string process, int percent = 0);

        public readonly Guid DFU_CP_UUID = new Guid("0000fff1-0000-1000-8000-00805f9b34fb");
        public readonly Guid DFU_DATA_UUID = new Guid("0000fff2-0000-1000-8000-00805f9b34fb");

        const int DFU_TIMEOUT = 1000;

        readonly Bean Bean;
        readonly ICharacteristic cp_char, data_char;
        readonly Semaphore cp_ntf_sem;
        readonly Mutex cp_ntf_mux;
        readonly Queue<byte[]> cp_ntf_que;

        public Upgrade(Bean bean)
        {
            Bean = bean;
            if (Bean == null)
                throw new Exception("No valid device");

            cp_char = Bean.Transport.FindCharacteristicById(DFU_CP_UUID);
            data_char = Bean.Transport.FindCharacteristicById(DFU_DATA_UUID);
            if (cp_char == null || data_char == null)
                throw new Exception("No valid characteristic");
            cp_char.WriteType = CharacteristicWriteType.WithoutResponse;
            data_char.WriteType = CharacteristicWriteType.WithoutResponse;

            cp_ntf_sem = new Semaphore(0, 1);
            cp_ntf_mux = new Mutex();
            cp_ntf_que = new Queue<byte[]>();
        }

        void NotifyHandler(object sender, CharacteristicUpdatedEventArgs e)
        {
            ICharacteristic characteristic = e.Characteristic;
            byte[] val = characteristic.Value;

            if (characteristic == cp_char)
            {
                cp_ntf_mux.WaitOne();
                cp_ntf_que.Enqueue(val);
                cp_ntf_sem.WaitOne(0);
                cp_ntf_sem.Release();
                cp_ntf_mux.ReleaseMutex();
            }
        }

        byte[] WaitNotify(ICharacteristic characteristic, int timeout)
        {
            byte[] data = null;

            if (characteristic == cp_char)
            {
                if (cp_ntf_que.Count == 0)
                {
                    if (!cp_ntf_sem.WaitOne(timeout))
                        return null;
                }
                cp_ntf_mux.WaitOne();
                data = cp_ntf_que.Dequeue();
                cp_ntf_sem.WaitOne(0);
                cp_ntf_mux.ReleaseMutex();
            }

            return data;
        }

        void ClearNotifyQueue()
        {
            cp_ntf_que.Clear();
            cp_ntf_sem.WaitOne(0);
        }

        async Task<uint> InquiryVersion()
        {
            uint version = 0;

            await Bean.Transport.Write(cp_char, new byte[] { (byte)DFU_OPERATION.INQUIRY, (byte)DFU_INQUIRY.VERSION });
            byte[] ack = WaitNotify(cp_char, DFU_TIMEOUT);
            if (ack != null && ack.Length == 2 && ack[0] == (byte)DFU_OPERATION.INQUIRY)
                version = ack[1];

            return version;
        }

        async Task<uint> InquiryBulkSize()
        {
            uint bulksize = 0;

            await Bean.Transport.Write(cp_char, new byte[] { (byte)DFU_OPERATION.INQUIRY, (byte)DFU_INQUIRY.BULKMAX });
            byte[] ack = WaitNotify(cp_char, DFU_TIMEOUT);
            if (ack != null && ack.Length == 5 && ack[0] == (byte)DFU_OPERATION.INQUIRY)
                bulksize = BitConverter.ToUInt32(ack, 1);

            return bulksize;
        }

        async Task<bool> DfuOperateStart(DFU_TYPE type, byte[] firmware)
        {
            bool start_ok = false;

            byte[] image_type_bitarray = { (byte)type };
            byte[] image_size_bitarray = BitConverter.GetBytes(firmware.Length);
            byte[] image_crc_bitarray = BitConverter.GetBytes(CRC.GetCRC32(firmware));
            byte[] start_bitarray = new byte[10];

            start_bitarray[0] = (byte)DFU_OPERATION.START;
            image_type_bitarray.CopyTo(start_bitarray, 1);
            image_size_bitarray.CopyTo(start_bitarray, 2);
            image_crc_bitarray.CopyTo(start_bitarray, 6);

            await Bean.Transport.Write(cp_char, start_bitarray);
            byte[] ack = WaitNotify(cp_char, DFU_TIMEOUT);
            if (ack != null && ack.Length == 2 && ack[0] == (byte)DFU_OPERATION.START)
                start_ok = ack[1] == (byte)DFU_ACK.OK;

            return start_ok;
        }

        async Task<bool> DfuOperateReceive(uint bulksize)
        {
            bool receive_ok = false;

            byte[] bulk_size_bitarray = BitConverter.GetBytes(bulksize);
            byte[] receive_bitarray = new byte[5];

            receive_bitarray[0] = (byte)DFU_OPERATION.SYNC;
            bulk_size_bitarray.CopyTo(receive_bitarray, 1);

            await Bean.Transport.Write(cp_char, receive_bitarray);
            byte[] ack = WaitNotify(cp_char, DFU_TIMEOUT);
            if (ack != null && ack.Length == 2 && ack[0] == (byte)DFU_OPERATION.SYNC)
                receive_ok = ack[1] == (byte)DFU_ACK.OK;

            return receive_ok;
        }

        async Task<bool> DfuOperateTransfer(byte[] firmware, uint bulkmax, ProgressEvent progress, CancellationToken ct)
        {
            progress?.Invoke("升级中");

            uint filesize = (uint)firmware.Length;
            uint transize = 0;
            long m_stick = DateTime.Now.Ticks;
            uint m_tlen = 0;
            double m_spd = 0;
            do
            {
                uint bulksend = 0;
                uint bulksize = filesize - transize;
                if (bulksize > bulkmax) bulksize = bulkmax;

                do
                {
                    uint pktsize = bulksize - bulksend;
                    if (pktsize > Bean.Transport.MTU - 3)
                        pktsize = (uint)Bean.Transport.MTU - 3;

                    byte[] pktbuff = new byte[pktsize];
                    Array.Copy(firmware, transize + bulksend, pktbuff, 0, pktsize);
                    bulksend += pktsize;

                    await Bean.Transport.Write(data_char, pktbuff);
                } while (bulksend < bulksize);
                transize += bulksize;

                await Task.Yield();

                if (transize == bulksize)
                    continue;

                byte[] ack = WaitNotify(cp_char, DFU_TIMEOUT);
                if (ack == null || ack.Length != 2 || ack[0] != (byte)DFU_OPERATION.SYNC || ack[1] != (byte)DFU_ACK.OK)
                    break;

                long dt = DateTime.Now.Ticks - m_stick;
                if (dt / 10 > 1000 * 1000)
                {
                    m_spd = (double)(transize - m_tlen) / (double)(dt * (1e-7)) / (double)1e3;
                    m_stick += dt;
                    m_tlen = transize;
                }

                progress?.Invoke($"升级中 {m_spd:F2}KB/S", (int)(100 * transize / filesize));
            } while (transize < filesize && !ct.IsCancellationRequested);

            if (transize == filesize)
            {
                byte[] ack = WaitNotify(cp_char, DFU_TIMEOUT);
                if (ack != null && ack.Length == 2 && ack[0] == (byte)DFU_OPERATION.SYNC && ack[1] == (byte)DFU_ACK.OK)
                    return true;
            }

            return false;
        }

        async Task<bool> DfuOperateStop()
        {
            bool stop_ok = false;

            await Bean.Transport.Write(cp_char, new byte[] { (byte)DFU_OPERATION.STOP });
            byte[] ack = WaitNotify(cp_char, DFU_TIMEOUT);
            if (ack != null && ack.Length == 2 && ack[0] == (byte)DFU_OPERATION.STOP)
                stop_ok = ack[1] == (byte)DFU_ACK.OK;

            if (stop_ok)
            {
                ack = WaitNotify(cp_char, DFU_TIMEOUT);
                if (ack != null && ack.Length == 2 && ack[0] == (byte)DFU_OPERATION.TERMINAL)
                    stop_ok = ack[1] == (byte)DFU_ACK.DONE;
            }

            return stop_ok;
        }

        async Task<bool> DfuOperateActive(DFU_TYPE type)
        {
            bool active_ok = false;

            byte[] image_type_bitarray = { (byte)type };
            byte[] active_bitarray = new byte[2];

            active_bitarray[0] = (byte)DFU_OPERATION.ACTIVE;
            image_type_bitarray.CopyTo(active_bitarray, 1);

            await Bean.Transport.Write(cp_char, active_bitarray);
            byte[] ack = WaitNotify(cp_char, DFU_TIMEOUT);
            if (ack != null && ack.Length == 2 && ack[0] == (byte)DFU_OPERATION.ACTIVE)
                active_ok = ack[1] == (byte)DFU_ACK.OK;

            return active_ok;
        }

        public async Task<bool> Run(DFU_TYPE type, byte[] firmware, ProgressEvent progress = null, CancellationToken ct = default)
        {
            bool success = false;
            uint version = 0;
            uint bulksize = 0;
            ClearNotifyQueue();

            progress?.Invoke("准备中");
            await Bean.Transport.EnableNotify(cp_char);
            cp_char.ValueUpdated += NotifyHandler;
            await Task.Delay(20);

            progress?.Invoke("握手中");
            // DFU_INQUIRY_VERSION
            version = await InquiryVersion();
            if (version != 1 || ct.IsCancellationRequested)
                goto error;
            await Task.Delay(20);

            // DFU_INQUIRY_BULK_MAX
            bulksize = await InquiryBulkSize();
            if (bulksize == 0 || ct.IsCancellationRequested)
                goto error;
            await Task.Delay(20);

            // DFU_OP_START
            if (await DfuOperateStart(type, firmware) == false || ct.IsCancellationRequested)
                goto error;
            await Task.Delay(20);

            // DFU_OP_RECEIVE
            if (await DfuOperateReceive(bulksize) == false || ct.IsCancellationRequested)
                goto error;
            await Task.Delay(100);
            if (await DfuOperateTransfer(firmware, bulksize, progress, ct) == false)
                goto error;
            await Task.Delay(20);

            // DFU_OP_STOP
            if (await DfuOperateStop() == false)
                goto error;
            await Task.Delay(20);

            // DFU_OP_ACTIVE
            if (await DfuOperateActive(type) == false || ct.IsCancellationRequested)
                goto error;
            await Task.Delay(20);

            success = true;

        error:
            if (ct.IsCancellationRequested)
                await DfuOperateStop();
            cp_char.ValueUpdated -= NotifyHandler;
            await Bean.Transport.DisableNotify(cp_char);
            return success;
        }
    }
}